package com.zrkizzy.common.redis.service.impl;

import com.zrkizzy.common.core.utils.StringUtil;
import com.zrkizzy.common.redis.exception.CacheErrorCode;
import com.zrkizzy.common.redis.service.IRedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis操作接口实现类
 *
 * @author zhangrongkang
 * @since 2023/3/9
 */
@Service
@Slf4j
public class RedisServiceImpl implements IRedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 设置键值对
     *
     * @param key   键
     * @param value 值
     * @param time  超时时间（秒）
     */
    @Override
    public void set(String key, Object value, long time) {
        redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 设置键值对（永不过期）
     *
     * @param key   键
     * @param value 值
     */
    @Override
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 获取键值对
     *
     * @param key 键
     * @return 值
     */
    @Override
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取键值对
     *
     * @param key 键
     * @return 值
     */
    @Override
    public <T> T get(String key, Class<T> clazz) {
        return clazz.cast(get(key));
    }

    /**
     * 删除键值对操作
     *
     * @param key 键
     * @return 是否删除成功
     */
    @Override
    public Boolean del(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 批量清除指定前缀的Key
     *
     * @param pattern 指定前缀
     */
    @Override
    public void clearKeys(String pattern) {
        // 获取指定前缀的所有Key
        Set<String> keys = scanKeys(pattern);
        for (String key : keys) {
            // 清除指定Key
            del(key);
        }
    }

    /**
     * 判断键是否存在
     *
     * @param key 键
     * @return 对应键是否存在
     */
    @Override
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * Redis执行Lua脚本
     *
     * @param redisScript Lua脚本
     * @param keyList     Redis中Key值队列
     * @param objects     多个参数
     * @return <T> Lua脚本返回值
     */
    @Override
    public <T> T execute(RedisScript<T> redisScript, List<String> keyList, Object... objects) {
        return redisTemplate.execute(redisScript, keyList, objects);
    }

    /**
     * 获取指定前缀的所有Key
     *
     * @param pattern 指定前缀
     * @return 所有指定前缀开头的key
     */
    @Override
    public Set<String> scanKeys(String pattern) {
        // 默认返回所有符合条件的Key
        return scanKeys(pattern, null);
    }

    /**
     * 获取指定前缀与数量的Key
     *
     * @param pattern 指定前缀
     * @param count 返回key的数量
     * @return 指定数量的Key
     */
    @Override
    public Set<String> scanKeys(String pattern, Integer count) {
        // 默认情况下仅定义前缀
        ScanOptions options = ScanOptions.scanOptions().match(pattern + "*").build();
        if (null != count) {
            options = ScanOptions.scanOptions()
                    // 前缀
                    .match(pattern)
                    // 每次返回数量
                    .count(count).build();
        }
        Cursor<String> cursor = redisTemplate.scan(options);
        // 定义Set集合
        Set<String> result = new HashSet<>();
        while (cursor.hasNext()) {
            result.add(cursor.next());
        }
        // 返回当前所有Key
        return result;
    }

    /**
     * 将指定集合存储到Redis中并设置有效时间
     *
     * @param key  Key
     * @param list 集合
     * @param time 超时时间
     */
    @Override
    public <T> void setList(String key, List<T> list, long time) {
        // 存储数据
        setList(key, list);
        // 设置过期时间，单位时间为秒
        redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 递减对应Key的值
     *
     * @param key 键
     * @return 递减后的值
     */
    @Override
    public Long decrement(String key) {
        return redisTemplate.opsForValue().decrement(key);
    }

    /**
     * 递增对应key的值
     *
     * @param key 键
     * @return 递增后的值
     */
    @Override
    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key);
    }

    /**
     * 获取Key值的失效时间
     *
     * @param key 键
     * @return 失效时间（秒）
     */
    @Override
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 将指定集合存储到Redis中
     *
     * @param key  Key
     * @param list 集合
     */
    @Override
    public <T> void setList(String key, List<T> list) {
        // 校验参数键值
        validateKeyAndCollectionValue(key, list);
        redisTemplate.opsForList().rightPushAll(key, list.toArray());
    }

    /**
     * 校验Key和集合类型Value
     *
     * @param key Key
     * @param collection 集合
     * @param <T> 集合元素类型
     */
    private <T> void validateKeyAndCollectionValue(String key, List<T> collection) {
        if (StringUtil.isBlank(key)) {
            throw CacheErrorCode.NO_VALID_KEY_PROVIDED.exception();
        }
        if (CollectionUtils.isEmpty(collection)) {
            throw CacheErrorCode.PROVIDE_INVALID_VALUE.exception();
        }
    }

    /**
     * 获取集合数据
     *
     * @param key   Key
     * @param start 起始位置
     * @param end   结束位置
     * @param clazz 集合元素类型
     * @return 集合数据
     */
    @Override
    public <T> List<T> getListValues(String key, int start, int end, Class<T> clazz) {
        // 获取当前集合数据
        List<Object> range = getList(key, start, end);
        List<T> resultList = new ArrayList<>();
        // 不为空则进行特殊处理
        for (Object object : range) {
            // 转换每一个对象为指定类型
            T convertObj = clazz.cast(object);
            resultList.add(convertObj);
        }
        return resultList;
    }

    /**
     * 获取指定Key的类型
     *
     * @param key Key
     * @return Key的数据类型
     */
    @Override
    public String type(String key) {
        // 获取当前类型
        DataType type = redisTemplate.type(key);
        return type != null ? type.name() : null;
    }

    /**
     * 获取指定类型集合
     *
     * @param key   Key
     * @param clazz 集合元素类型
     * @return 指定类型集合
     */
    @Override
    public <T> List<T> getList(String key, Class<T> clazz) {
        // 方法复用，-1表示返回所有内容
        return getListValues(key, 0, -1, clazz);
    }

    /**
     * 获取集合指定数据
     *
     * @param key   Key
     * @param start 起始位置
     * @param end   结束位置
     * @return 集合数据
     */
    @Override
    public List<Object> getList(String key, int start, int end) {
        List<Object> range = redisTemplate.opsForList().range(key, start, end);
        // 集合数据为空则返回空集合
        if (CollectionUtils.isEmpty(range)) {
            return new ArrayList<>();
        }
        return range;
    }

    /**
     * 获取指定集合
     *
     * @param key Key
     * @return 指定集合
     */
    @Override
    public List<Object> getList(String key) {
        return getList(key, 0, -1);
    }

    /**
     * 获取数据库大小
     *
     * @return Redis数据库大小
     */
    @Override
    public Object getDataBaseSize() {
        return redisTemplate.execute((RedisCallback<Object>) connection -> connection.serverCommands().dbSize());
    }

    /**
     * 获取Redis具体配置信息
     *
     * @param param Redis内容参数
     * @return Redis配置参数
     */
    @Override
    public Properties getProperties(String param) {
        if (StringUtil.isEmpty(param)) {
            return (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.serverCommands().info());
        }
        return (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.serverCommands().info(param));
    }
}